package de.tuberlin.dima.dkey.server;


import de.tuberlin.dima.dkey.util.GraphNotSameException;
import de.tuberlin.dima.dkey.util.GEdge;
import de.tuberlin.dima.dkey.util.GraphViz;

import de.tuberlin.dima.dkey.nodes.KeyNode;
import de.tuberlin.dima.dkey.nodes.WordNode;
import de.tuberlin.dima.dkey.nodes.GNode;
import de.tuberlin.dima.dkey.nodes.TextNode;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.HashMap;

import org.apache.log4j.Logger;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.lang.reflect.*;


public class TreeDB {


	static Logger logger = Logger.getLogger(TreeDB.class);
	
	private static HashMap<String , HashSet<Integer>> nodeList = new HashMap<String,HashSet<Integer>>();
	private static HashMap<Integer, LinkedList<GEdge>> edgeList = new HashMap<Integer, LinkedList<GEdge>>();
	private static HashMap<Integer, LinkedList<GNode>> graphNodeList = new HashMap<Integer, LinkedList<GNode>>();
	
	//private GConfig config = null;
	
	private String dbName;
	
	public TreeDB(){
		//config = new GConfig();
		//config.setRemoveCapitalisation(true);
	}
	
	public HashMap<String , HashSet<Integer>>  getNodeList(){
		return nodeList;
	}
	
	public HashMap<Integer, LinkedList<GEdge>> getEdgeList(){
		return edgeList;
	}
	
	public HashMap<Integer, LinkedList<GNode>> getGraphNodeList(){
		return graphNodeList;
	}

	public String getDbName() {
		return dbName;
	}

	public void setDbName(String dbName) {
		if (dbName == null)
			throw new NullPointerException("dbName must not be null");
	
		this.dbName = dbName;
	}
	
	public void loadDB(String fileName) throws FileNotFoundException{
		
		BufferedReader input =  new BufferedReader(new FileReader(fileName));

		try {
			this.addTreeDkey(input);
		} catch (IOException e) {
			e.printStackTrace();
		}
		setDbName(fileName);
		
	}
	
	public String serialize(){
		return serializeDkey(graphNodeList.keySet());
	}
	
	
	public String serializeJson(Collection<Integer> treeKeys){
		
		StringBuffer output = new StringBuffer();
		output.append("{ \"graphs\": [");
		//Save all nodes based on the Graphs
		Boolean firstRun = true;
		for (int i : treeKeys){
			
			if (firstRun){firstRun = false;}else{output.append(",");}
			
			//Check if the graph exists
			if(!this.graphNodeList.keySet().contains(i)){
				output.append("{\"error\":\""+ i +"\"}" );
				continue;
			}
			
			
			output.append("{");
			output.append("\"id\":\"" + i + "\"," );
			//Loop through all the graphs and save all the nodes
			output.append("\"nodes\": [" );
			firstRun = true;
			for (GNode node : graphNodeList.get(i)){
				if (firstRun){firstRun = false;}else{output.append(",");}
				output.append("{");
				output.append("\"id\":\""+ node.getId() + "\"," );
				output.append("\"value\":\""+ node.getValue() + "\"," );
				output.append("\"type\":\""+ node.getType() + "\"" );	
				output.append("}");
			}
			output.append(" ]," );
			
			output.append("\"edges\": [" );
			firstRun = true;
			for (GEdge edge : edgeList.get(i)){
				if (firstRun){firstRun = false;}else{output.append(",");}
				output.append("{");
				output.append("\"source\":\""+ edge.getSource() + "\"," );
				output.append("\"sink\":\""+ edge.getSink() + "\"" );
				output.append("}");				
			}
			output.append(" ]" );
			
			output.append("}");
		}
		output.append("]}");
		
		return output.toString();
	}

	public String serializeDkey(Collection<Integer> treeKeys){
		
		StringBuffer output = new StringBuffer();

		for (int i : treeKeys){
			
			//Check if the graph exists
			if(!this.graphNodeList.keySet().contains(i)){
				output.append("{\"error\":\""+ i +"\"}" );
				continue;
			}
			

			for (GNode node : graphNodeList.get(i)){
				output.append("n;" + i + ";" );
				output.append(node.getId() + ";" );
				output.append(node.getValue().replace(";", "---SEMI---") + ";" );
				output.append(node.getType());	
				output.append("\n");
			}
			

			for (GEdge edge : edgeList.get(i)){
				output.append("e;" + i + ";" );
				output.append(edge.getSource() + ";" );
				output.append(edge.getSink());
				output.append("\n");		
			}

		}

		return output.toString();
	}

	
	
	public void saveDB(String fileName) throws IOException{
		
	    BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
	    out.write(serialize());
	    out.close();
	}
	
	
	public String addTreeJson(String treeJson){
		
		JsonParser parser = new JsonParser();
		JsonArray array;

		try{
			array = parser.parse(treeJson).getAsJsonObject().get("graphs").getAsJsonArray();
		}catch(IllegalStateException e){
			logger.error("JSon Parser Error: " + e);
			return "{\"error\": \"JSon Parser Error: " + e +"\"}";
		}
		
		for (int i =0 ; i < array.size();i++){
			//Looping through the graphs now

			JsonObject graph = array.get(i).getAsJsonObject();
			
			if(this.graphNodeList.keySet().contains(graph.get("id").getAsInt())){
				   logger.info("Trying to import a tree that is already in the db");
				   return "Error (There is already a tree with the same hash)";
			}
			
			
			HashMap<Integer, GNode> tmpList = new HashMap<Integer, GNode>();
			for (JsonElement node : graph.get("nodes").getAsJsonArray()){
				
				try {
					GNode tmpNode;
					@SuppressWarnings("rawtypes")
					Class cls =  Class.forName("de.tuberlin.dima.dkey.nodes." + node.getAsJsonObject().get("type").getAsString());
					tmpNode = (GNode) cls.getConstructors()[1].newInstance(graph.get("id").getAsInt(), node.getAsJsonObject().get("id").getAsInt(), node.getAsJsonObject().get("value").getAsString());
					this.addBaseNode(tmpNode);
					tmpList.put(node.getAsJsonObject().get("id").getAsInt(), tmpNode);
				} catch (Exception e) {
					//e.printStackTrace();
					logger.error("Node type unknown doing nothing: " + node.getAsJsonObject().get("type"));
				}
				
				
				
				//We need this for the edges

			}
			
			//System.out.println(graph.get("edges").getAsJsonArray() );
			for (JsonElement edge : graph.get("edges").getAsJsonArray()){
				try {
					this.addBaseEdge(tmpList.get(edge.getAsJsonObject().get("source").getAsInt()), tmpList.get(edge.getAsJsonObject().get("sink").getAsInt()));
				} catch (GraphNotSameException e) {
					logger.error("The Graphs are not the same. Database file is corrupted. (A edge always has to be in one graph");
					e.printStackTrace();
				}
			}
			
		}

		return "Ok";
		
	}
	

	
	public String addTreeDkey(BufferedReader input) throws IOException{

		while (input != null){
			//Split up line
			String line = input.readLine();

			if(line == null)
				break;

			String[] fields = line.split(";");
			if(fields[0].equals("n")){
				try {
					GNode tmpNode;
					@SuppressWarnings("rawtypes")
					Class cls =  Class.forName("de.tuberlin.dima.dkey.nodes." + fields[4]);
					tmpNode = (GNode) cls.getConstructors()[1].newInstance(Integer.parseInt(fields[1]), Integer.parseInt(fields[2]), fields[3]);

					this.addBaseNode(tmpNode);

				} catch (Exception e) {
					//e.printStackTrace();
					logger.error("Node type unknown doing nothing: " + e);
				}
				
				
			}else if(fields[0].equals("e")){
				
				int graph = Integer.parseInt(fields[1]);
				
				GEdge edge = new GEdge(graph, Integer.parseInt(fields[2]), Integer.parseInt(fields[3])	);


				
				if (edgeList.get(graph) == null){
					LinkedList<GEdge> tmpList = new LinkedList<GEdge>();
					tmpList.add(edge);
					edgeList.put(graph,tmpList);
				}else{
					edgeList.get(graph).add(edge);
				}
				
				
			}else{
				logger.error("Somehow the import is wrong; Don't understand: " + fields[0]);
			}
			
		}
		
		return "Ok";
	}

	
	
	private GNode addBaseNode(GNode node){
		
		for(String indexValue : node.getIndexValues()){

			if (nodeList.get(indexValue) == null) {
				HashSet<Integer> tmpList = new HashSet<Integer>();
				tmpList.add(node.getGraph());
				nodeList.put(indexValue, tmpList);

			}else{
				if ( ! nodeList.get(indexValue).contains(node.getGraph())){
					nodeList.get(indexValue).add(node.getGraph());
				}
			}
		}		
		if(graphNodeList.get(node.getGraph()) == null){
			//First node in the Graph
			LinkedList<GNode> tmpList = new LinkedList<GNode>();
			tmpList.add(node);
			graphNodeList.put(node.getGraph(), tmpList);
		}else{
			graphNodeList.get(node.getGraph()).add(node);
		}
		
		return node;
	}
	
	private GEdge addBaseEdge(GNode source, GNode sink) throws GraphNotSameException{
		
		//Check if the 2 nodes are in the same graph
		if(source.getGraph() != sink.getGraph()){
			throw new GraphNotSameException("Graph and Sink are not in the same Graph");
		}
		
		int graph = source.getGraph();
		
		GEdge edge = new GEdge(graph, source.getId(), sink.getId()	);
		
		
		if (edgeList.get(graph) == null){
			LinkedList<GEdge> tmpList = new LinkedList<GEdge>();
			tmpList.add(edge);
			edgeList.put(graph,tmpList);
		}else{
			edgeList.get(graph).add(edge);
		}
		return edge;
	}
	
	//*********************DEBUG***********************************************/
	public String debug(){
		StringBuffer output = new StringBuffer();
		
		output.append("Node List\n");
		output.append(nodeList.toString());
		output.append("\n\n");
		output.append("Link List\n");
		output.append(edgeList.toString());
		
		return output.toString();
	}
	
	public void paint(String fileName, int... graphsList) {		
		GraphViz gv = new GraphViz();
		gv.addln(gv.start_graph());
		for (int counter : graphsList ){
			gv.addln("subgraph cluster" +counter+ " {");	
			for ( GNode node : graphNodeList.get(counter)){
				gv.addln("node" + node.getId() + counter + "  [label=\"" + node.getId() + ":" + node.getValue() + " \"];" );
			}
			
			for (GEdge edge : edgeList.get(counter) ){
				gv.addln("node"+edge.getSource()+counter+ "-> node"+edge.getSink()+counter);
			}
			gv.addln("}");
		}

		gv.addln(gv.end_graph());
		
		String type = "png";
		File out = new File(fileName + "." + type);
		gv.writeGraphToFile(gv.getGraph(gv.getDotSource(), type), out);

	}
	
	public String dbInfo(){
		int nodes = 0;
		int edges = 0;
		int graphs = 0;
		for (int counter : graphNodeList.keySet() ){
			graphs++;
			edges += this.edgeList.get(counter).size();
			nodes += this.graphNodeList.get(counter).size();
		}
		
		return "Nodes: "+ nodes + "; Edges: " + edges + "; Graphs: " + graphs;
	}
}
